package com.superatom.springframework.boot;

import com.superatom.springframework.beans.BeanUtils;
import com.superatom.springframework.context.ApplicationContextInitializer;
import com.superatom.springframework.context.ApplicationListener;
import com.superatom.springframework.context.ConfigurableApplicationContext;
import com.superatom.springframework.core.ResourceLoader;
import com.superatom.springframework.core.annotation.AnnotationAwareOrderComparator;
import com.superatom.springframework.core.io.support.SpringFactoriesLoader;
import com.superatom.springframework.util.Assert;
import com.superatom.springframework.util.ClassUtils;
import com.superatom.springframework.util.StopWatch;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * @author Kwokchiu Wong
 * @Title:
 * @Package
 * @Description:
 * @date 2020/10/16 3:12 下午
 */
public class SpringApplication {

    private static final Log logger = LogFactory.getLog(SpringApplication.class);

    private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

    private boolean headless = true;

    private ResourceLoader resourceLoader;

    private Set<Class<?>> primarySources;

    private WebApplicationType webApplicationType;

    private List<ApplicationContextInitializer<?>> initializers;

    // main方法所在的类
    private Class<?> mainApplicationClass;

    // 监听者
    private List<ApplicationListener<?>> listeners;

    public SpringApplication(Class<?>... primarySources){
        this(null, primarySources);
    }

    /**
     * 此方法主要做了：
     * 1. 确定资源加载器
     * 2. 实例化spring.factories里列出的Bean容器（Application Context）
     * 3. 实例化spring.factories里列出的ApplicationListener实现类
     * @param resourceLoader
     * @param primarySources
     */
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources){
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources不能为空");
        this.primarySources = new LinkedHashSet<Class<?>>(Arrays.asList(primarySources));
        // 根据已经加载的类Springboot类型
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        // 实例化所有Bean容器（Application Context）（见spring.factories文件）
        setInitializers((Collection) getSpringFactoriesInstances(
                ApplicationContextInitializer.class));
        // 实力化所有ApplicationListener的实现类（见spring.factories文件）
        setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
        // 查询main方法所在的类
        this.mainApplicationClass = deduceMainApplicationClass();
    }

    /**
     * 推断main函数所在类（这个方法妙，借助调用函数堆栈数组）
     * @return
     */
    private Class<?> deduceMainApplicationClass() {
        try {
            // 新建一个运行时异常对象，通过这个对象获取当前的调用函数堆栈数组
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            // 遍历这个堆栈数组
            for (StackTraceElement stackTraceElement : stackTrace) {
                // 查询方法名为main的类
                if ("main".equals(stackTraceElement.getMethodName())) {
                    // 返回此类
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        }
        catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

    public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
        this.listeners = new ArrayList<>();
        this.listeners.addAll(listeners);
    }

    public void setInitializers(
            Collection<? extends ApplicationContextInitializer<?>> initializers) {
        this.initializers = new ArrayList<>();
        // 存放所有的Bean容器（Application Context）
        this.initializers.addAll(initializers);
    }


    /**
     * 获取所有Spring工厂（Bean容器/Application Context）
     * @param type
     * @param <T>
     * @return
     */
    private <T> Collection<T>  getSpringFactoriesInstances(Class<T> type){
        // type = ApplicationContextInitializer.class
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    /**
     * 根据给出的接口名称获取相应的实现类的类名并且实例化
     * @param type
     * @param parameterTypes
     * @param args
     * @param <T>
     * @return
     */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
                                                          Class<?>[] parameterTypes, Object... args) {
        // type = ApplicationContextInitializer.class

        // 获取类加载器
        ClassLoader classLoader = getClassLoader();
        // 在已经加载的类中查询含有ApplicationContextInitializer的类名
        Set<String> names = new LinkedHashSet<>(
                SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 实例化这些Application Context类（先把Bean容器实例化）
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                classLoader, args, names);
        // 排序
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

    private <T> List<T> createSpringFactoriesInstances(Class<T> type,
                                                       Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
                                                       Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        // 加载ApplicationContextInitializer类
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass
                        .getDeclaredConstructor(parameterTypes);
                // 调用默认构造器实例化类
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException(
                        "Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }

    /**
     * 如果资源类加载器为空，使用当前线程类加载器
     * @return
     */
    public ClassLoader getClassLoader() {
        if (this.resourceLoader != null) {
            return this.resourceLoader.getClassLoader();
        }
        return ClassUtils.getDefaultClassLoader();
    }



    /**
     * 静态启动类，默认设置（入口）
     * @param primarySource
     * @param args
     */
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args){
        return run(new Class<?>[] { primarySource }, args);
    }

    /**
     * 静态启动类，默认设置
     * @param primarySources
     * @param args
     */
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String... args){
        // new SpringApplication(primarySources)加载初始化资源
        return new SpringApplication(primarySources).run(args);
    }

    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        // 实例化所有实现了SpringApplicationRunListener接口的类
        // 这里加载org.springframework.boot.context.event.EventPublishingRunListener类
        Collection<SpringApplicationRunListener> springApplicationRunListeners = getSpringFactoriesInstances(
                SpringApplicationRunListener.class, types, this, args);
        return new SpringApplicationRunListeners(logger, springApplicationRunListeners);
    }

    /**
     * 运行Spring应用，创建或者刷新ApplicationContext
     * @param args
     * @return
     */
    public ConfigurableApplicationContext run(String... args) {
        // 创建计时对象
        StopWatch stopWatch = new StopWatch();
        // 开始计时
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        // 记录报错
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);

        return null;
    }

    private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
                SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }
}
